#include <QFile>
#include <QDebug>
#include <QChar>
#include <QDir>
#include <QTime>

#include "dico.h"


//-----------------------------------------------------------
Dico::Dico (QObject *parent, bool damerau) :
	QObject (parent)
{
	m_useDamerauTest = damerau;

	m_convertionTable.insert ("'", "");	// remplacer l'apostrophe par rien
	m_convertionTable.insert ("-", "");	// remplacer le tiret par rien
	m_convertionTable.insert ("à", "A");
	m_convertionTable.insert ("ä", "A");
	m_convertionTable.insert ("â", "A");
	m_convertionTable.insert ("ä", "A");
	m_convertionTable.insert ("é", "E");
	m_convertionTable.insert ("è", "E");
	m_convertionTable.insert ("ê", "E");
	m_convertionTable.insert ("ë", "E");
	m_convertionTable.insert ("î", "I");
	m_convertionTable.insert ("ï", "I");
	m_convertionTable.insert ("ô", "O");
	m_convertionTable.insert ("ö", "O");
	m_convertionTable.insert ("ù", "U");
	m_convertionTable.insert ("û", "U");
	m_convertionTable.insert ("ü", "U");
	m_convertionTable.insert ("æ", "AE");
	m_convertionTable.insert ("œ", "OE");
	m_convertionTable.insert ("ç", "C");
	m_convertionTable.insert ("ÿ", "Y");
	m_convertionTable.insert ("ŷ", "Y");
	m_convertionTable.insert ("ã", "A");
	m_convertionTable.insert ("ẽ", "E");
	m_convertionTable.insert ("ĩ", "Y");
	m_convertionTable.insert ("ñ", "N");
	m_convertionTable.insert ("õ", "O");
	m_convertionTable.insert ("ũ", "U");
	m_convertionTable.insert ("ṽ", "V");
	m_convertionTable.insert ("ỹ", "Y");
	m_convertionTable.insert ("ß", "SS");
	m_convertionTable.insert ("ì", "I");
	m_convertionTable.insert ("í", "I");
	m_convertionTable.insert ("ò", "O");
	m_convertionTable.insert ("ó", "O");
	m_convertionTable.insert ("á", "A");
	m_convertionTable.insert ("á", "A");
	m_convertionTable.insert ("í", "I");
	m_convertionTable.insert ("ú", "U");

	for (char car = 'a' ; car <= 'z' ; car++)
	{
		m_convertionTable.insert (QString (car), (QString (car)).toUpper ());
	}

	clear ();

}
//-----------------
void Dico::clear ()
{
	m_contentList.clear ();
	m_currentRefDico.clear ();
}
//-----------------------------------------------------
void Dico::init (const QString& path, bool keepAnnexe)
{
	clear ();
	loadFrom (path);
	QFileInfo fileInfo (path);
	m_currentRefDico = fileInfo.fileName ();
	if (keepAnnexe)
	{
		addFrom (Config::annexeDico ());
	}
}
//------------------------------------------------------
void Dico::loadFrom (const QString & path)
{
	clear ();
	addFrom (path, true);
}
//----------------------------
void Dico::beginCalc (const int max)
{
	QApplication::setOverrideCursor (QCursor (Qt::WaitCursor));
	m_maxItems = max;
	emit initCalc (m_maxItems);
	QApplication::processEvents ();
	m_calcStep = 0;
	m_calcTime.start ();
}
//-------------------------------
void Dico::updateCalc (int index)
{
	m_calcStep ++;
	if (m_calcTime.elapsed () >= 1000 && m_calcStep % 1000 == 0)
	{
		emit progress (index == 0 ? m_calcStep : index);
		QApplication::processEvents ();
	}
}
//-------------------
void Dico::endCalc ()
{
	emit stopCalc (m_calcTime.elapsed ());
	QApplication::processEvents ();
	QApplication::restoreOverrideCursor ();
	qDebug () << QString ("Exécuté en %1 ms").arg (m_calcTime.elapsed ());
}

//---------------------------------------------------------
void Dico::addFrom (const QString &path, bool isTrusty)
{
	QFile file (path);

	if (!file.open (QIODevice::ReadOnly | QIODevice::Text))
	{
		emit warning (tr ("Je ne peux pas ouvrir le fichier %1").arg (path));
		return;
	}

	beginCalc (file.size ());

	int readSize = 0;
	QString word;

	QTextStream in (&file);
	in.setCodec ("UTF-8");

	if (isTrusty)
	{
		QString buffer = in.readAll ();
		m_contentList += buffer.split ('\n', QString::SkipEmptyParts);
	}
	else
	{
		while (not in.atEnd ())
		{
			word = normalize (in.readLine ());

			if (word.length () >= Config::MinWordLength)
				m_contentList << word;

			readSize += (word.length () + 1);
			updateCalc (readSize);
		}

		m_contentList.removeDuplicates ();

	}

	dicoStats ();

	endCalc ();
}

//-----------------------------------------------
QString Dico::normalize (const QString &wordToNormalize)
{
	QString line (wordToNormalize.simplified ().toLower ());

	// traitement spécifique du dico Firefox


	foreach (const QChar& sep, separators ())
	{
		int pos = line.indexOf (sep);
		if (pos != -1)
		{
			line.truncate (pos);
		}
	}

	int len = line.length ();
	QString result;

	if (len >= Config::MinWordLength)
	{
		int current = 0;
		bool ok = true;

		while (ok && current < len)
		{
			const QString& car (line.mid (current, 1));
			ok = m_convertionTable.contains (car);
			if (not ok)
				qDebug () << tr ("Je ne reconnais pas le caractère %1").arg (car);
			else
			{
				result += /*m_convertionTable.value*/ (car);
				current++;
			}
		}

		if ((not ok) or (result.length () < Config::MinWordLength)) result.clear ();
	}

	return result;
}
//----------------------------------------------
void Dico::find (const QString &target, bool anyLength)
{
	static const QString aster = "*";

	if (target.isEmpty () || target == aster) return;

	if (anyLength) {
		find (target + aster, false);
		return;
	}

	QRegExp regExp (target, Qt::CaseSensitive, QRegExp::Wildcard);

	find (regExp, false);
}
//----------------------------
void Dico::save (const QString &path)
{
	QFile file (path);

	if (not file.open (QIODevice::WriteOnly))
	{
		emit error (tr ("Vous ne pouvez ni créer ni remplacer le fichier\n%1")
					.arg (path));
		return;
	}

	beginCalc ();
	QApplication::setOverrideCursor (QCursor (Qt::WaitCursor));
	QTextStream out (&file);
	out.setCodec ("UTF-8");

	foreach (const QString &word, m_contentList) {
		out << word << endl;
	}
//    out << m_contentList.join ("\n");

	file.close ();
	QApplication::restoreOverrideCursor ();
	endCalc ();
}
//----------------------------------
void Dico::addToAnnexe (const QString &word)
{
	QFile annexe (Config::annexeDico ());

	if (!annexe.open (QIODevice::Append))
	{
		emit error (tr ("Impossible de compléter le dico annexe"));
		return;
	}
	QTextStream out (&annexe);

	out.setCodec ("UTF-8");

	out << word << '\n';
	annexe.close ();
}
//----------------------------------------
QMap<QString, QIcon> Dico::refDicosList ()
{
	QDir dir (Config::refDicsPath ());


	dir.setNameFilters (Config::dicoFileFilter ());

	QMap<QString, QIcon> ret;

	foreach (QString dicoName, dir.entryList ())
	{
		QFileInfo fileInfo (dicoName);
		QString name (fileInfo.fileName ());
		if (officialRefDicosList ().contains (name))
		{
			ret.insert (name, languageIcon (languageString (name)));
		}
		else
		{
			ret.insert (name, personalDicIcon ());
		}
	}

	return ret;
}
//----------------------------------------
QStringList Dico::officialRefDicosList ()
{
	QDir dir (Config::officialRefDicosPath ());


	dir.setNameFilters (Config::dicoFileFilter ());

	QStringList ret;

	foreach (const QString &dicoName, dir.entryList ())
	{
		QFileInfo fileInfo (dicoName);
		ret << fileInfo.fileName ();
	}

	return ret;
}
//------------------------
int Dico::averageLength ()
{
	int count = m_contentList.count ();

	if (count == 0) return 0;

	int totalLength = 0;

	foreach (const QString& word, m_contentList)
	{
		totalLength += word.length ();
	}

	return totalLength / count;

}
//---------------------
void Dico::dicoStats ()
{
	if (size () < 2)
	{
		emit dicoInfos (0, 0);
		emit message (tr ("Dictionnaires chargés : %1 mot").arg (size ()));
	}
	else
	{
		int min = 100;
		int max = 0;

		foreach (const QString& word, m_contentList)
		{
			const int length = word.length ();

			min = qMin (min, length);
			max = qMax (max, length);
		}
		emit dicoInfos (min, max);
		emit message (tr ("Dictionnaires chargés : %1 mots").arg (size ()));
	}
}
//--------------------------------------------------------------
void Dico::find (const QString &target, int mode, int distMax)
{
	QStringList result;
	const int targetLen = target.length ();

	switch (mode)
	{
	case Contains:
	{
		beginCalc (size ());
		result = m_contentList.filter (target);
		endCalc ();
		emit xMessage (true, result);
		return;
	}
		break;

	case LookingLike:
	{
		QStringList result;

		beginCalc (size ());

		int min = 0;

		if (dicsContains (target))
		{
			result << target;
		}
		else
		{
			StringTool tool;

			const int distMax = (targetLen + 1) / 2;	// distance "raisonnable" !
			QHash<int, QString> hash;

			foreach (const QString& word, m_contentList)
			{
				int dlDist = tool.distance (m_useDamerauTest, target, word, distMax);
				if (dlDist != -1)
					hash.insertMulti (dlDist, word);
			}

			min = hash.keys ().first ();
			result = hash.values (min);
		 }

		QHash<QString, QString> d (decomp (target));
		if (not d.isEmpty ())
		{
			foreach (QString key, d.keys ())
			{
				result << tr ("%1 + %2").arg (key, d.value (key));
			}
		}

		endCalc ();

		result.removeDuplicates ();

		QString msg (tr (" à la distance %1").arg (min));

		if (result.isEmpty ()) msg.clear ();

		emit xMessage (true, result/*, msg*/);
		return;
	}
		break;

	case DistantFrom:
	{
		QHash<int, QString> hash;

		beginCalc (size ());

		StringTool tool;

		foreach (const QString& word, m_contentList)
		{
			int dist = tool.distance (m_useDamerauTest, target, word, distMax);
			if (dist >= 0)
				hash.insertMulti (dist, QString ("%1 (%2)").arg (word).arg (dist));
		}

		endCalc ();

		for (int count = 0 ; count <= distMax ; count++)
		{
			result.append (hash.values (count));
		}

		QString msg (tr (" (distance entre parenthèses)"));

		if (result.isEmpty ())
			msg.clear ();

		emit xMessage (true, result, msg);
		return;
	}
		break;

	}

	QString orderedTarget (StringTool::ordered (target));
	QString orderedUniqueTarget (StringTool::orderedUnique (target));

	beginCalc (size ());

	foreach (const QString& word, m_contentList)
	{
		bool test = false;
		int wordLen = word.length ();

		switch (mode)
		{
		case StartsWith:
			test = wordLen >= targetLen and word.startsWith (target);
			break;

		case EndsWith:
			test = wordLen >= targetLen and word.endsWith (target);
			break;

		case NotContains:
			test = wordLen < targetLen or not word.contains (target);
			break;

		case ContainsAllLetters:
			if (wordLen >= targetLen)
			{
				for (QString::ConstIterator iter = orderedUniqueTarget.constBegin () ; iter != orderedUniqueTarget.constEnd () ; iter++)
				{
					const QChar& letter (*iter);
					test = word.count (letter) == target.count (letter);
					if (!test) break;
				}
			}
			break;

		case ContainsNoLetter:
			if (wordLen >= targetLen)
			{
				for (QString::ConstIterator iter = orderedUniqueTarget.constBegin () ; iter != orderedUniqueTarget.constEnd () ; iter++)
				{
					const QChar& letter (*iter);
					test = ! word.contains (letter);
					if (!test) break;
				}
			}
			break;

		case LeMotLePlusLong:
			if (wordLen <= targetLen)
			{
				QString tmp (StringTool::orderedUnique (word));
				for (QString::ConstIterator iter = tmp.constBegin () ; iter != tmp.constEnd () ; iter++)
				{
					const QChar& letter (*iter);
					test = word.count (letter) <= target.count (letter);
					if (! test) break;
				}
			}
			break;

		case Anagrams:
			test = word.length () == targetLen and orderedTarget == StringTool::ordered (word);
			break;


		default:
			break;
		}

		if (test)
		{
			result << word;
		}
	}

	endCalc ();

	emit xMessage (true, result);
}
//---------------------------------------------
void Dico::find (const QRegExp& target, bool extended)
{
	QStringList result;

	beginCalc (size ());

	int index = m_contentList.indexOf (target);

	while (index != -1)
	{
		result << m_contentList.at (index);
		index = m_contentList.indexOf (target, index + 1);
	}

	endCalc ();

	emit xMessage (extended, result);
}
//--------------------------------------------
QIcon Dico::languageIcon (const QString& languageCode)
{
	QIcon baseIcon (Config::dicIcon ());

	QPixmap basePixmap (baseIcon.pixmap (QSize (30,30)));

	QPainter painter (&basePixmap);

	painter.drawPixmap (0, 0, QPixmap (":/images/flags/" + languageCode + ".png"));

	painter.end ();

	return QIcon (basePixmap);
}
//----------------------------------------------
QString Dico::languageString (const QString &name)
{
	FileInfo fileInfo (name);

	QString bname = fileInfo.name ();
	int pos1 = bname.lastIndexOf ("_");

	if (pos1 >= 0) {
		return bname.mid (pos1 + 1);
	} else {
		return QString ();
	}
}
//----------------------------
QIcon Dico::personalDicIcon ()
{
	// construction de l'icône "dico perso"
	QIcon baseIcon (Config::dicIcon ());

	QPixmap basePixmap (baseIcon.pixmap (QSize (30,30)));

	QPainter painter (&basePixmap);

	painter.drawPixmap (0, 0, QPixmap (":/images/user.png"));

	painter.end ();

	return QIcon (basePixmap);

}
//----------------------------------------------------------
QHash<QString, QString> Dico::decomp (const QString &target)
{
	QHash<QString, QString> result;
	int targetLen = target.length ();

	QList<QString> list = m_contentList;

	beginCalc ();

	foreach (const QString& word, list) {

		int wordLen = word.length ();

		if (targetLen > wordLen and target.startsWith (word)) {
			const QString& match1 (target.mid (wordLen));
			if (dicsContains (match1)) {
				result.insert (word, match1);
			}
		}
	}
	endCalc ();
	return result;
}
